This chapter describes the design theory and the structure of the code that AppMaker generates.
AppMaker strongly encourages a separation of UI code from functional code. An application is built around data structures that form an abstract model of the contents of a document. The UI code displays and modifies these data structures without having any knowledge of the logic behind the data. The application logic manipulates the data without having any knowledge of the user interface.
Within AppMaker you define UI items and data items, then you connect them. The generated code automatically keeps UI values and data values in sync with each other. A change in a UI item will change its target data item, and a change in a data item will change any UI items that are targeted to that data item.
Most of your hand-written functional code will deal with the abstract data model rather than with UI items. You may not need to add any code to the generated UI code.
Within AppMaker you define UI items and data items, then you connect them. You define data items by first creating DataDefs (Data Definitions) and then creating data Members within DataDefs.
You connect UI to data by specifying a particular DataDef for the Application, the Document, each Window, and each Dialog. Then for each window item you specify its TargetData, a Member of the parent window's (or dialog's) DataDef.
Once you have connected a UI item to a data item, AppMaker will generate code for the UI item to initialize itself from the data item, to respond to user interactions by changing the data item, and to respond to changes in the data item by changing the UI item. The effect is that the UI item and the data item are kept in sync with each other.
Multiple window items can connect to the same data item. E.g. there could be an edit field and a popup connected to a string data item. Choosing from the popup will store into the string data which will notify the edit field that the data has changed. The effect is that the popup UI item has modified the edit text UI item.
Each Document has an associated DataDef, which by convention is named DocData. All windows are connected to the Document's data. Each dialog, by convention, has its own DataDef. If you do not define DocData, AppMaker's code generator will create it for you. If you do not define a dialog's data, AppMaker's code generator will create it for you if needed. (Some simple dialogs don't have any data.) If you do not connect a UI item to a data item then the code generator will automatically create a data Member and connect the UI item to that data item. Afterwards you can modify the generated DataDefs and their members, and connect UI items to data items of your choice.
While the implementation of an application differs from one language to another, the structure is generally the same. AppMaker generates separate modules for each DataDef, the Application, the Document, each Window, and each Dialog. AppMaker also generates Command handling code which is distributed among the other modules.
We begin with Data classes because they form the core of an application. Most of the other modules access Data.
AppMaker generates a separate data module for each DataDef. The module contains:
A DataDef is a Signaler; it sends a signal whenever a data item is changed. The signal indicates what DataDef object was changed and which data item was changed. Each DataDef object maintains a list of responders, other objects that want to know when data is changed. Windows and Dialogs are responders. Each Window or Dialog registers and unregisters itself with its data object so that it can update UI items whenever data items are changed.
Each data item (data, pseudo-data, or persistent data) has Get and Set accessors. The generated code for a Get function returns the value of the data member (except for pseudo-data). The Set function stores into the data member and sends a DataChanged message to any Windows or Dialogs that are registered as users of the Data.
For pseudo-data items there are no data members so AppMaker does not generate the fetch or store but does generate the DataChanged signal. In the Member Info dialog you can provide code fragments to implement the Get and/or the Set. For real (not pseudo) data items you can also provide Get and/or Set code fragments to do more than just fetch or store data.
The primary use of data items is as TargetData for UI items. For any UI item you can specify its TargetData either via the item's Info dialog or via the Info floater. The generated code for a UI item calls its TargetData item's Get function to initialize the UI item, responds to user interactions and calls the TargetData's Set function, and responds to DataChanged signals by calling the Get function to update the UI display.
A second use of a data item is to enable or disable a UI item. You can specify an EnablingData item via the Info floater. AppMaker generates code to enable or disable the UI item in response to changes in the data item. The EnablingData item will often be a pseudo-data item that determines its value based on the validity of other data items.
A data item can be an array of pointers to other data objects. The AppMaker library provides a language-specific array class. (For PowerPlant it is an LArray.) The array class sends an ArrayChanged signal whenever in element of the list is inserted, removed, or changed. A List UI item can specify an array data item as SourceData. The listbox displays each element of the array in a separate row and responds to ArrayChanged signals by updating the display.
DataDefs form the skeleton and the sinews of an application. Most of the functional processing of an application will be in code fragments of Get or Set accessors or of Function Members. Additional logic will be in code fragments of Command handlers to initialize data items before calling a modal dialog or doing something with modified data after the dialog returns. In all of these code fragments you are usually dealing with binary data items and not with UI items. The generated code automatically keeps UI items and data items in sync with each other. To change the value of a UI item your code will change a data item and let the normal DataChanged signaling mechanism update the UI items for you.
The App(lication) module handles application-level processing such as New, Open, and Quit. Most of the code is actually in its parent class, a library module, since it tends to be much the same from one application to another.
The App module responds to the New command by creating a new Doc object and telling it to create new data. App responds to the Open command by displaying the standard file dialog, then creating a new Doc object and telling it to open the selected file.The Doc(ument) module handles document-level processing such as Save and Print. AppMaker's convention is to treat all windows as belonging to a document. The generated code responds to a New or Open command by opening whatever windows you have defined.
Within AppMaker, a Document is connected to a DataDef, normally named DocData. The generated code for a Document creates an instance of DocData and passes it to each window. All windows of a document therefore access or modify the same data. The generated and library code implements the Open and Save commands by calling DocData's ReadFromFile and WriteToFile methods.
AppMaker generates a separate module for each window. The window code creates the window's controls, initializes each control from its target data item, responds to user interaction by setting data values, and responds to data changes by updating control values.
Within AppMaker (in the Window Info dialog) you specify a DataDef that is connected to the Window. It should be DocData, the DataDef you specified as connected to the Document. A future release may allow you to specify a different DataDef. The UI items should have TargetData items that are Members of the Window's DataDef (i.e. DocData).
The generated code has a similar structure for all of AppMaker's languages:
AppMaker generates a separate module for each dialog. The general model for a modal dialog is that the caller creates a data structure, passes it to the dialog module which displays the current data values, interacts with the user to set new data values, then returns a true or false to indicate OK or Cancel. The caller then examines the changed values and implements the purpose of the dialog.
Within AppMaker (in the Dialog Info dialog) you specify a DataDef that is connected to the Dialog. Normally, each dialog will have its own DataDef. The UI items should have TargetData items that are Members of the Dialog's DataDef.
The generated code for a dialog is similar to that for a window:
The generated code to call a modal dialog is language-specific in its details but has this general structure:
Command handling is distributed among App, Doc, Windows, and Dialogs. A Command can be invoked by a menu item, a button, or by double-clicking a listbox. In theory, any other UI item can invoke a command but that is not common.
Within AppMaker you can specify that a Command performs a code fragment or invokes a dialog. For a dialog, you can provide a code fragment to be executed before calling the dialog, and a code fragment to be executed if a modal dialog returns true.
Because Command handlers are generated inside other modules, e.g. in Window modules, they can access that module's data.
The easiest way to write a code fragment is to write it in the generated source file, compile and test it, then Copy and Paste it from the source file into AppMaker's Command Info dialog.